package com.abel.bigwater.tlv.model

import com.alibaba.fastjson.annotation.JSONField
import com.alibaba.fastjson.serializer.DateCodec
import com.alibaba.fastjson.serializer.JSONSerializer
import io.netty.buffer.ByteBuf
import org.slf4j.LoggerFactory
import java.time.LocalDateTime
import kotlin.experimental.and

class TlvPlatformPacket() : TlvDataApi {
    val head: Byte = 0x68

    /** at least 1 byte length */
    var body: TlvDevBody? = null

//    @JSONField(serializeUsing = DateCodec::class)
    var timestamp: LocalDateTime? = null

    var checksum: Short = 0
    val tail: Byte = 0x16

    @JSONField(serializeUsing = TlvExceptionSerializer::class)
    var parseResult: TlvException? = null

    private val log = LoggerFactory.getLogger(TlvPlatformPacket::class.java)

    companion object {
        private val MIN_LEN = 1 + (0) + 6 + 1 + 1
    }

    override fun finalLen(): Int {
        return MIN_LEN + (body?.finalLen() ?: TlvDevBody.MIN_LEN)
    }

    /**
     * buf should be cleared or consumed.
     */
    override fun fill(buf: ByteBuf): ByteBuf {
        finalLen()

        // buf may not be consumed, so write to a new buffer firstly
        var result: ByteArray? = null
        with(buf.alloc().buffer()) {
            writeByte(head.toInt())

            body!!.fill(this)

            timestamp = timestamp ?: LocalDateTime.now()
            writeBytes(TlvHelper.toByteArray(timestamp!!))

            checksum = 0
            this.duplicate().let {
                val ba = ByteArray(it.readableBytes())
                it.readBytes(ba)
                ba
            }.forEach { checksum = (checksum + it).toShort() and 0xFF }

            writeByte(checksum.toInt())
            writeByte(tail.toInt())

            result = ByteArray(readableBytes())
            this.readBytes(result)
        }

        // fill to buf in whole.
        log.info("Plain text for platform: ${TlvHelper.toHexString(result)}")
        buf.writeBytes(TlvHelper.encryptAes(result!!))

        return buf
    }

    override fun parse(buf: ByteBuf): ByteBuf {
        if (buf.readableBytes() < MIN_LEN + TlvDevBody.MIN_LEN)
            throw TlvInvalidException("not enough bytes for platform: ${buf.readableBytes()}")

        // consumed bytes.
        val ba = ByteArray(buf.readableBytes())
        buf.readBytes(ba)

        val result = TlvHelper.decryptAes(ba)
        log.info("Plain text for Platform: ${TlvHelper.toHexString(result)}")

        var tailIdx = result.size - 1
        // find tail
        for (idx in (result.size - 1) downTo (MIN_LEN + TlvDevBody.MIN_LEN - 1)) {
            if (result[idx] == tail) {
                tailIdx = idx
                break
            }
        }
        if (result.first() != head || tailIdx == result.size - 1) throw TlvInvalidException("BAD head or tail: ${result.first()}-${result[tailIdx]}")

        // checksum & tail.
        checksum = result[tailIdx - 1].toShort()
        var cs2: Short = 0
        result.copyOfRange(0, tailIdx - 1).forEach { cs2 = (cs2 + it).toShort() and 0xFF }
        log.warn("Checksum: computed ${cs2} == ${checksum} in PDU?")

        with(buf.alloc().buffer().writeBytes(result.copyOfRange(1, tailIdx - 6 - 1))) {
            // body
            try {
                body = TlvDevBody(dataList = ArrayList())
                body?.parse(this)
            } catch (ex: TlvException) {
                parseResult = ex
                log.error("解析平台消息失败: ${ex.message}", ex)
            }
        }

        timestamp = TlvHelper.toLocalDateTime(result.copyOfRange(tailIdx - 1 - 6, tailIdx - 1))

        return buf
    }
}